""""Contains (roughly) all self-written mathematical functions used, as well as a Vector & VectorMatrix class.

    Most - not necessarily all of this - is somehow linear algebra."""


from numpy import matrix
from scipy.linalg import det,norm
from scipy import arccos
from copy import copy, deepcopy
from array import array


def big_product(values):
    result = 1

    for v in values:
        result *= v

    return result


def dot_product(v1, v2):
    result = 0
    # Todo: check for range (error handling)
    for i in range(len(v1)):
        result += v1[i] * v2[i]
    return result



def cross_product(*vectors):

    """Performs the general cross-product
    for n-1 linearily independent vectors of dimension n."""
#   if len(vectors) is not len(vectors[0]):
#    # Todo: do something to deal with error
#    pass
#   else:

    result = copy(vectors[0])

    sign = 1
    for v in xrange(len(vectors) + 1):
        result[v] = sign * det(submatrix(vectors, v))
        sign *= -1

    return result

def submatrix(vectors, index):
    matlist = []
    for vector in vectors:
        v1 = vector[:index]
        v1.extend(vector[index+1:])
        matlist.append(v1)

    return matrix(matlist)


def scalar_prod(scalar, vector):
    result = copy(vector)
    for i in range(len(vector)):
        result[i] *= scalar
    return result

def normalize(vector):
    return scalar_prod(1 / norm(vector), vector)

def lin_dependent(v1, v2):
    return normalize(v1) == normalize(v2)

def angle(v1, v2):
    return arccos(dot_product(normalize(v1), normalize(v2)))

def lin_combine(v1, v2):
    result = copy(v1)
    for i in range(len(v1)):
        result[i] += v2[i]
    return result

def substract(v1, v2):
    result = copy(v1)
    for i in range(len(v1)):
        result[i] -= v2[i]
    return result

def add_all(entries):
    result = 0
    for entry in entries:
        result += entry

    return result


class Vector(array):
    def __new__(cls, items = [], *args, **keyw):

        return array.__new__(cls,'d', items)

    def __init__(self, items = []):
        array.__init__(self, 'd', items)


    def __add__(self,v1):
        # TODO: check type
        return lin_combine(self, v1)

    def __sub__(self, v1):
        return substract(self, v2)

    def __mul__(self, v2):
        return dot_product(self,v2)

    def __copy__(self):
        result = type(self)(self)
        result.__dict__ = copy(self.__dict__)
        return result

    def __deepcopy__(self, memo = {}):
        result = type(self)(self)
        result.__dict__ = deepcopy(self.__dict__, memo)
        return result


#    def __getslice__(self, i, j):
#        return Vector(list.__getslice__(self, i,j))


class VectorMatrix(Vector):
    def __new__(self, *args, **kwds):
        return Vector.__new__(self, *args, **kwds)

    def __mul__(self, vector):
        result = copy(vector)
        self.applyTo(result)
        return result

    def applyTo(self, vector):
        l = len(vector)
        for i in xrange(l):
            vector[i] = dot_product(self[i*l:(i+1)*l], vector)


    def testOn(self, vector):
        return self * vector

